package com.inexas.cu.client.mvp;

import java.util.*;
import com.inexas.cu.client.mvp.ListNotification.*;

public class TupleListImpl<T extends Tuple> extends MemberImpl implements TupleList<T> {
	private Binder binder;
	
	public TupleListImpl(String key) {
	    super(key);
    }

	private final List<T> list = new ArrayList<T>();

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#onChanged(com.inexas.cu.client.mvp.Source, com.inexas.cu.client.mvp.Notification)
     */
	public void onChanged(Source source, Notification notification) {
		fire(source, notification);
	}
	
	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#add(com.inexas.cu.client.mvp.Source, T)
     */
	public boolean add(Source source, T tuple) {
		final ListNotification notification =  new ListNotification(Type.INSERT, list.size(), 1);
		list.add(tuple);
		binder.bind(this, tuple);
		fire(source, notification);
		return true;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#add(com.inexas.cu.client.mvp.Source, int, T)
     */
	public void add(Source source, int index, T tuple) {
		final ListNotification notification = new ListNotification(Type.INSERT, index, 1);
		list.add(index, tuple);
		binder.bind(this, tuple);
		tuple.addObserver(this);
		fire(source, notification);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#addAll(com.inexas.cu.client.mvp.Source, java.util.Collection)
     */
	public boolean addAll(Source source, Collection<T> collection) {
		final boolean result;
		
		final int size = collection.size();
		if(size == 0) {
			result = false;
		} else {
			final ListNotification notification = new ListNotification(Type.INSERT, list.size(), size);
			list.addAll(collection);
			for(final T tuple : collection) {
				tuple.addObserver(this);
				binder.bind(this, tuple);
			}
			fire(source, notification);
			result = true;
		}
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#addAll(com.inexas.cu.client.mvp.Source, int, java.util.Collection)
     */
	public boolean addAll(Source source, int index, Collection<T> collection) {
		final boolean result;
		
		final int size = collection.size();
		if(size == 0) {
			result = false;
		} else {
			final ListNotification notification = new ListNotification(Type.INSERT, index, size);
			list.addAll(index, collection);
			for(final T tuple : collection) {
				tuple.addObserver(this);
				binder.bind(this, tuple);
			}
			fire(source, notification);
			result = true;
		}
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#clear(com.inexas.cu.client.mvp.Source)
     */
	public void clear(Source source) {
		if(list.size() > 0) {
			for(final T tuple : list) {
				tuple.removeObserver(this);
				binder.unbind(this, tuple);
			}
			list.clear();
			final ListNotification notification = new ListNotification(Type.INSERT, 0, 0);
			fire(source, notification);
		}
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#remove(com.inexas.cu.client.mvp.Source, T)
     */
	public boolean remove(Source source, T tuple) {
		final int index = removeAndUnobserve(tuple);
		final boolean result = index >= 0;
		if(result) {
			final ListNotification notification = new ListNotification(Type.DELETE, index, 1);
			fire(source, notification);
		}
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#remove(com.inexas.cu.client.mvp.Source, int)
     */
	public T remove(Source source, int index) {
		final T result = list.remove(index);
		final ListNotification notification = new ListNotification(Type.DELETE, index, 1);
		binder.unbind(this, result);
		fire(source, notification);
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#removeAll(com.inexas.cu.client.mvp.Source, java.util.Collection)
     */
	public boolean removeAll(Source source, Collection<T> toRemoveCollection) {
		boolean result = false;
		for(final T toRemove : toRemoveCollection) {
			if(removeAndUnobserve(toRemove) >= 0) {
				result = true;
			}
		}
		if(result) {
			final ListNotification notification = new ListNotification(Type.RESET, 0, list.size());
			fire(source, notification);
		}
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#retainAll(com.inexas.cu.client.mvp.Source, java.util.Collection)
     */
	public boolean retainAll(Source source, Collection<T> collection) {
		boolean result = false;
		// Backwards so that removing tuples doesn't change anything...
		final int size = list.size();
		for(int i = size - 1; i >= 0; i--) {
			final T tuple = list.get(i);
			if(!collection.contains(tuple) ) {
				list.remove(i);
				binder.unbind(this, tuple);
				result = true;
			}
		}
		if(result) {
			final ListNotification notification = new ListNotification(Type.RESET, 0, list.size());
			fire(source, notification);
		}
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#set(com.inexas.cu.client.mvp.Source, int, T)
     */
	public T set(Source source, int index, T tuple) {
		final T result = list.set(index, tuple);
		binder.unbind(this, result);
		final ListNotification notification = new ListNotification(Type.UPDATE, index, 1);
		fire(source, notification);
		return result;
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#contains(T)
     */
	public boolean contains(T tuple) {
		return list.contains(tuple);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#containsAll(java.util.Collection)
     */
	public boolean containsAll(Collection<T> collection) {
		return list.containsAll(collection);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#get(int)
     */
	public T get(int index) {
		return list.get(index);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#indexOf(T)
     */
	public int indexOf(T tuple) {
		return list.indexOf(tuple);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#isEmpty()
     */
	public boolean isEmpty() {
		return list.isEmpty();
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#iterator()
     */
	public Iterator<T> iterator() {
		// !todo Consider using a non-mutable collection
		return list.iterator();
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#lastIndexOf(T)
     */
	public int lastIndexOf(T tuple) {
		return list.lastIndexOf(tuple);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#listIterator()
     */
	public ListIterator<T> listIterator() {
		// !todo Consider using a non-mutable collection
		return list.listIterator();
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#listIterator(int)
     */
	public ListIterator<T> listIterator(int index) {
		// !todo Consider using a non-mutable collection
		return list.listIterator(index);
    }

	/* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#size()
     */
	public int size() {
		return list.size();
    }

    /* (non-Javadoc)
     * @see com.inexas.cu.client.mvp.TupleList#toArray()
     */
    public T[] toArray() {
    	@SuppressWarnings("unchecked")
		final T[] result = (T[])new Object[list.size()];
		return list.toArray(result);
    }

	/**
	 * Remove a tuple if it's in the list. If we do remove it
	 * then remove ourselves as observer
	 * 
	 * @param toRemove the tuple to remove
	 * @return -1 if not found else the index of the tuple removed
	 */
	private int removeAndUnobserve(T toRemove) {
		final int result = list.indexOf(toRemove);
		if(result >= 0) {
			final T removed = list.remove(result);
			binder.unbind(this, removed);
		}
		return result;
    }

	public void setBinder(Binder binder) {
		this.binder = binder;
    }

}
